<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Frame;

use Exception;
use Srv\Libs\Common\CommJson;
use Srv\Libs\Common\CommString;
use Srv\Libs\Common\CommTime;
use Srv\Libs\ConfModel\TokenKeyConf;

final class AuthData
{
    private static int $uId                         = 0;            // 用户Id
    private static string $tokenKey                 = '';           // 令牌Key
    private static string $devId                    = '';           // 认证设备Id
    private static string $appPlatform              = '';           // 认证设备平台
    private static string $authStr                  = '';           // 认证权限数据
    private static bool $authSuper                  = false;        // 认证超级管理员
    private static int $tokenKeyExpireTime          = 0;            // 令牌Key有效期
    private static ?TokenKeyConf $TokenKeyConf      = null;         // TokenKeyConf
    private static int $cacheTokenKeyTime           = 600;          // Cache令牌存储时长[秒]
    private static string $cacheTokenKeyPrefix      = 'AUTH_';      // Cache令牌存储Key前缀
    private static int $authKeyExpireTime           = 600;          // 认证权限KEY与索引Id缓存时长[秒]
    private static array $authKeyList               = [];           // 认证权限KEY与索引Id

    /**
     * 重置
     */
    final public static function reset():void
    {
        self::$uId                  = 0;
        self::$tokenKey             = '';
        self::$tokenKeyExpireTime   = 0;
        self::$appPlatform          = '';
        self::$devId                = '';
        self::$authStr              = '';
        self::$authSuper            = false;
        if(!(self::$TokenKeyConf instanceof TokenKeyConf)) self::$TokenKeyConf = Conf::getTokenKeyConf();
    }

    /**
     * @param int $uId
     * @param string $tokenKey
     * @param int $tokenKeyExpireTime
     * @param string $appPlatform
     * @param string $devId
     * @param string $authStr
     * @return bool
     * 恢复认证状态
     */
    final public static function restore(int $uId, string $tokenKey, int $tokenKeyExpireTime, string $appPlatform, string $devId, string $authStr):bool
    {
        self::reset();
        if($uId < 1 || strlen($tokenKey) < 1) return false;
        if(!(self::$TokenKeyConf instanceof TokenKeyConf)) self::$TokenKeyConf = Conf::getTokenKeyConf();
        $nonceInt                   = 0;
        if(!self::parseTokenKey($tokenKey, $nonceInt)) return false;
        $checkResult                = true;
        if(self::$TokenKeyConf->isCheckCache() || self::$TokenKeyConf->isCheckDb()){
            $checkResult            = false;
            $isNextCheck            = true;
            $currTime               = CommTime::getTimeStamp(0);
            if(self::$TokenKeyConf->isCheckCache()){    // check Cache
                $tokenKeyData               = self::getCacheTokenKeyData($tokenKey, $uId);
                if(isset($tokenKeyData['expireTime']) && isset($tokenKeyData['uId']) && isset($tokenKeyData['nonceInt'])){
                    $isNextCheck            = false;
                    $tokenKeyExpireTime     = $tokenKeyData['expireTime'];
                    if($tokenKeyData['expireTime'] > $currTime && $tokenKeyData['uId'] === $uId && $tokenKeyData['nonceInt'] === $nonceInt) $checkResult = true;
                }
            }
            if($isNextCheck && self::$TokenKeyConf->isCheckDb()){       // check Db
                $tokenKeyData               = self::getDbTokenKeyData($uId, $nonceInt);
                if(isset($tokenKeyData['expireTime']) && isset($tokenKeyData['uId']) && isset($tokenKeyData['nonceInt'])){
                    $tokenKeyExpireTime     = $tokenKeyData['expireTime'];
                    if($tokenKeyData['expireTime'] > $currTime && $tokenKeyData['uId'] === $uId && $tokenKeyData['nonceInt'] === $nonceInt) $checkResult = true;
                    if(self::$TokenKeyConf->isCheckCache()) self::setCacheTokenKeyData($tokenKey, $uId, $tokenKeyData) || self::setCacheTokenKeyData($tokenKey, $uId, $tokenKeyData);
                }
            }
        }
        if($checkResult){
            self::$uId                  = $uId;
            self::$tokenKey             = $tokenKey;
            self::$tokenKeyExpireTime   = $tokenKeyExpireTime;
            self::$appPlatform          = $appPlatform;
            self::$devId                = $devId;
            self::$authStr              = $authStr;
            self::$authSuper            = false;
        }
        return $checkResult;
    }

    /**
     * @param string $tokenString
     * @param string $devIdCurr
     * @param string $appPlatformCurr
     * @return bool
     * 初始化解码令牌
     */
    final public static function init(string $tokenString, string $devIdCurr, string $appPlatformCurr):bool
    {
        self::reset();
        if(strlen($tokenString) < 16) return false;
        $uId                        = 0;
        $tokenKey                   = '';
        $tokenKeyExpireTime         = 0;
        $devId                      = '';
        $appPlatform                = '';
        $authStr                    = '';
        $authSuper                  = false;
        if(!TokenKey::parseToken($tokenString, $uId, $tokenKey, $authStr, $devId, $appPlatform, $devIdCurr, $appPlatformCurr, $authSuper)) return false;
        $nonceInt                   = 0;
        if(!self::parseTokenKey($tokenKey, $nonceInt)) return false;
        $checkResult                = true;
        if(self::$TokenKeyConf->isCheckCache() || self::$TokenKeyConf->isCheckDb()){
            $checkResult            = false;
            $isNextCheck            = true;
            $currTime               = CommTime::getTimeStamp(0);
            if(self::$TokenKeyConf->isCheckCache()){                // check Cache
                $tokenKeyData               = self::getCacheTokenKeyData($tokenKey, $uId);
                if(isset($tokenKeyData['expireTime']) && isset($tokenKeyData['uId']) && isset($tokenKeyData['nonceInt'])){
                    $isNextCheck            = false;
                    $tokenKeyExpireTime     = $tokenKeyData['expireTime'];
                    if($tokenKeyData['expireTime'] > $currTime && $tokenKeyData['uId'] === $uId && $tokenKeyData['nonceInt'] === $nonceInt) $checkResult = true;
                }
            }
            if($isNextCheck && self::$TokenKeyConf->isCheckDb()){   // check Db
                $tokenKeyData               = self::getDbTokenKeyData($uId, $nonceInt);
                if(isset($tokenKeyData['expireTime']) && isset($tokenKeyData['uId']) && isset($tokenKeyData['nonceInt'])){
                    $tokenKeyExpireTime     = $tokenKeyData['expireTime'];
                    if($tokenKeyData['expireTime'] > $currTime && $tokenKeyData['uId'] === $uId && $tokenKeyData['nonceInt'] === $nonceInt) $checkResult = true;
                    if(self::$TokenKeyConf->isCheckCache()) self::setCacheTokenKeyData($tokenKey, $uId, $tokenKeyData) || self::setCacheTokenKeyData($tokenKey, $uId, $tokenKeyData);
                }
            }
        }
        if($checkResult){
            self::$uId                  = $uId;
            self::$tokenKey             = $tokenKey;
            self::$tokenKeyExpireTime   = $tokenKeyExpireTime;
            self::$appPlatform          = $appPlatform;
            self::$devId                = $devId;
            self::$authStr              = $authStr;
            self::$authSuper            = $authSuper;
        }
        return $checkResult;
    }

    /**
     * @param int $uId
     * @param string $authStr
     * @param string $deviceId
     * @param string $platform
     * @param bool $authSuper
     * @return array
     * 生成新的令牌
     */
    final public static function generateNewToken(int $uId, string $authStr, string $deviceId, string $platform, bool $authSuper = false):array
    {
        $tokenData                  = [];
        $setExpireTime              = max(self::$TokenKeyConf->getExpireTime(), 0);
        $setRenewTime               = max(self::$TokenKeyConf->getRenewTime(), 0);
        $currTime                   = CommTime::getTimeStamp();
        $nonceInt                   = 0;
        $tokenKey                   = self::generateNewTokenKey($nonceInt);
        $tokenStr                   = TokenKey::generateToken($uId, $tokenKey, $authStr, $deviceId, $platform, $authSuper);
        $tokenData['tokenStr']      = $tokenStr;
        $tokenData['expireTime']    = $setExpireTime < 1 ? 0 : $currTime + $setExpireTime;
        $tokenData['renewTime']     = $setRenewTime < 1 ? 0 : $currTime + min($setRenewTime, $setExpireTime);
        $tokenData['nonceInt']      = $nonceInt;
        $tokenData['tokenKey']      = $tokenKey;
        return $tokenData;
    }

    /**
     * @param int $uId
     * @param int $nonceInt
     * @return array
     * 获取数据存储数据Key
     */
    private static function getDbTokenKeyData(int $uId, int $nonceInt):array
    {
        $tokenKeyData       = ['expireTime' => 0, 'uId' => 0, 'nonceInt' => 0];
        if($uId < 1 || $nonceInt < 1) return $tokenKeyData;
        try{
            $DbAuto         = ControllerAbstract::getDbAuto();
            $expireTime     = $DbAuto->getDataInt('SELECT utnr_expire_time AS expire_time FROM '.$DbAuto->getTableNameAll('adm_user_token').' WHERE auir_id='.$uId.' AND utnr_nonce='.$nonceInt.' AND utnr_delete=0', 'expire_time');
            if($expireTime < 1 || $expireTime < CommTime::getTimeStamp(0)) return $tokenKeyData;
        }catch(Exception $Exception){
            return $tokenKeyData;
        }
        return ['expireTime' => $expireTime, 'uId' => $uId, 'nonceInt' => $nonceInt];
    }

    /**
     * @param string $authKey
     * @param int $authId
     * @param bool $authLock
     * @param int $authLevel
     * @return bool
     * 获取数据存储KEY的Id
     */
    private static function getAuthKeyData(string $authKey, int &$authId, bool &$authLock, int &$authLevel):bool
    {
        $authKeyLength              = strlen($authKey);
        if($authKeyLength < 1 || $authKeyLength > 64) return false;
        if(count(self::$authKeyList) < 1 || !isset(self::$authKeyList['expireTime']) || !isset(self::$authKeyList['list']) || self::$authKeyList['expireTime'] <= CommTime::getTimeStamp(0)){
            self::$authKeyList      = ['expireTime' => CommTime::getTimeStamp(0) + self::$authKeyExpireTime, 'list' => []];
            try{
                $DbAuto             = ControllerAbstract::getDbAuto();
                $authKeyRecord      = $DbAuto->getData('SELECT takr_id AS id, takr_key AS ukeys, takr_lock AS locks, takr_level AS levels FROM '.$DbAuto->getTableNameAll('adm_auth_key_record'));
                if(count($authKeyRecord) > 0) foreach ($authKeyRecord as $authKeyItem){
                    $takrId         = intval($authKeyItem['id']);
                    $takrKey        = trim($authKeyItem['ukeys']);
                    $takrLock       = intval($authKeyItem['locks']);
                    $takrLevel      = intval($authKeyItem['levels']);
                    $takrKeyLength  = strlen($takrKey);
                    if($takrId < 1 || $takrKeyLength < 1 || $takrKeyLength > 64) continue;
                    self::$authKeyList['list'][$takrKey] = ['id' => $takrId, 'lock' => $takrLock, 'level' => $takrLevel];
                }
            }catch(Exception $Exception){}
        }
        if(!isset(self::$authKeyList['list'][$authKey])) return false;
        $authId         = self::$authKeyList['list'][$authKey]['id'];
        $authLock       = self::$authKeyList['list'][$authKey]['lock'] === 1;
        $authLevel      = self::$authKeyList['list'][$authKey]['level'];
        if(!in_array($authLevel, [0, 1, 2], true)) $authLevel = 2;  // 等级[0-任意,1-仅需登入,2-必需权限]
        return true;
    }

    /**
     * @param string $tokenKey
     * @param int $uId
     * @return string
     * 获取缓存Key名
     */
    private static function getCacheTokenKey(string $tokenKey, int $uId):string
    {
        return self::$cacheTokenKeyPrefix.$uId.'_'.$tokenKey;
    }

    /**
     * @param string $tokenKey
     * @param int $uId
     * @return array
     * 获取令牌缓存值
     */
    private static function getCacheTokenKeyData(string $tokenKey, int $uId):array
    {
        $tokenKeyData       = [];
        $cacheKey           = self::getCacheTokenKey($tokenKey, $uId);
        try{
            $DataRedis      = ControllerAbstract::getCacheAuto();
            $tokenStr       = $DataRedis->get($cacheKey);
            if(is_string($tokenStr)) $tokenKeyData = CommJson::decodeArray($tokenStr);
        }catch(Exception $Exception){
            return $tokenKeyData;
        }
        return $tokenKeyData;
    }

    /**
     * @param string $tokenKey
     * @param int $uId
     * @param array $tokenKeyData
     * @return bool
     * 设置令牌缓存值
     */
    private static function setCacheTokenKeyData(string $tokenKey, int $uId, array $tokenKeyData):bool
    {
        $cacheKey           = self::getCacheTokenKey($tokenKey, $uId);
        try{
            $DataRedis      = ControllerAbstract::getCacheAuto();
            $result         = $DataRedis->set($cacheKey, CommJson::encodeArray($tokenKeyData), self::$cacheTokenKeyTime);
        }catch(Exception $Exception){
            return false;
        }
        return $result;
    }

    /**
     * @param string $tokenKey
     * @param int $uId
     * @return bool
     * 删除令牌缓存值
     */
    final public static function delCacheTokenKeyData(string $tokenKey, int $uId):bool
    {
        $cacheKey           = self::getCacheTokenKey($tokenKey, $uId);
        try{
            $DataRedis      = ControllerAbstract::getCacheAuto();
            $result         = $DataRedis->del($cacheKey) || $DataRedis->del($cacheKey) || $DataRedis->del($cacheKey);
        }catch(Exception $Exception){
            return false;
        }
        return $result;
    }

    /**
     * @param string $tokenKey
     * @param int $uId
     * @return bool
     * 删除API令牌缓存值
     */
    final public static function delCacheApiTokenKeyData(string $tokenKey, int $uId):bool
    {
        $cacheKey           = self::getCacheTokenKey($tokenKey, $uId);
        try{
            $DataRedisApi   = ControllerAbstract::getCacheApi();
            $result         = $DataRedisApi->del($cacheKey) || $DataRedisApi->del($cacheKey) || $DataRedisApi->del($cacheKey);
        }catch(Exception $Exception){
            return false;
        }
        return $result;
    }

    /**
     * @param string $tokenKey
     * @param int $uId
     * @param int $ttl
     * @return bool
     * 设置令牌缓存值
     */
    final public static function ttlCacheTokenKeyData(string $tokenKey, int $uId, int $ttl):bool
    {
        $cacheKey           = self::getCacheTokenKey($tokenKey, $uId);
        try{
            $DataRedis      = ControllerAbstract::getCacheAuto();
            $result         = $DataRedis->setExpire($cacheKey, $ttl) || $DataRedis->setExpire($cacheKey, $ttl) || $DataRedis->setExpire($cacheKey, $ttl);
        }catch(Exception $Exception){
            return false;
        }
        return $result;
    }

    /**
     * @return bool
     * 是否已登录
     */
    final public static function isLogin():bool
    {
        return self::$uId > 0 && self::$tokenKeyExpireTime > CommTime::getTimeStamp(0);
    }

    /**
     * @return int
     * 获取用户Id
     */
    final public static function getLoginUserId():int
    {
        return self::$uId;
    }

    /**
     * @return string
     * 获取用户令牌
     */
    final public static function getLoginTokenKey():string
    {
        return self::$tokenKey;
    }

    /**
     * @return string
     * 获取用户设备平台
     */
    final public static function getLoginAppPlatform():string
    {
        return self::$appPlatform;
    }

    /**
     * @return string
     * 获取用户设备Id
     */
    final public static function getLoginDevId():string
    {
        return self::$devId;
    }

    /**
     * @return string
     * 获取用户认证数据
     */
    final public static function getLoginAuthStr():string
    {
        return self::$authStr;
    }

    /**
     * @return int
     * 获取用户令牌Nonce
     */
    final public static function getLoginTokenNonce():int
    {
        if(strlen(self::$tokenKey) !== 16) return 0;
        return intval(substr(self::$tokenKey, 6, 10));
    }

    /**
     * @return int
     * 获取用户令牌有效期
     */
    final public static function getLoginTokenKeyExpireTime():int
    {
        return self::$tokenKeyExpireTime;
    }

    /**
     * @param string $authKey
     * @return bool
     * 检查自定义权限
     */
    final public static function checkAuth(string $authKey):bool
    {
        $authId         = 0;
        $authLock       = true;
        $authLevel      = -1;
        if(!self::getAuthKeyData($authKey, $authId, $authLock, $authLevel) || $authId < 1 || $authLock || $authLevel < 0) return self::$authSuper;
        if($authLevel === 0){       // 任意
            return true;
        }else if($authLevel === 1){ // 仅需登入
            return self::isLogin();
        }else{                      // 必需权限配置
            if(self::$authSuper) return true;
            if(strlen(self::$authStr) < 1) return false;
            return substr(CommString::char64ToBin(self::$authStr), $authId-1, 1) === '1';
        }
    }

    /**
     * @param string $textString
     * @param int $secNum
     * @return string
     * 加密字符串内容
     */
    private static function encryptString(string $textString, int $secNum):string
    {
        $textEncrypt        = $textString;
        do{
            $textEncrypt    = sha1($textEncrypt);
            $textEncrypt    = substr($textEncrypt.$textEncrypt, hexdec(substr($textEncrypt, 0, 1)), strlen($textEncrypt));
        }while(--$secNum > 0);
        return $textEncrypt;
    }

    /**
     * @param int $nonceInt
     * @return string
     * 生成新令牌Key
     */
    private static function generateNewTokenKey(int &$nonceInt):string
    {
        $nonceInt           = mt_rand(1000000000, 9999999999);
        $nonceString        = strval($nonceInt);
        $secNum             = min(max(intval(substr($nonceString, 0, 3)), 1), 999);
        $nonceEncrypt       = self::encryptString($nonceString, $secNum);
        return substr($nonceEncrypt, 0, 6).$nonceString;
    }

    /**
     * @param int $nonceInt
     * @return string
     * 获取令牌Key
     */
    final public static function getTokenKeyByNonce(int $nonceInt):string
    {
        $nonceString        = strval($nonceInt);
        $secNum             = min(max(intval(substr($nonceString, 0, 3)), 1), 999);
        $nonceEncrypt       = self::encryptString($nonceString, $secNum);
        return substr($nonceEncrypt, 0, 6).$nonceString;
    }

    /**
     * @param string $tokenKey
     * @param int $nonceInt
     * @return bool
     * 解析令牌Key
     */
    final public static function parseTokenKey(string $tokenKey, int &$nonceInt):bool
    {
        if(strlen($tokenKey) !== 16) return false;
        $nonceEncryptPrefix = substr($tokenKey, 0, 6);
        $nonceString        = substr($tokenKey, 6, 10);
        $secNum             = min(max(intval(substr($nonceString, 0, 3)), 1), 999);
        if(substr(self::encryptString($nonceString, $secNum), 0, 6) === $nonceEncryptPrefix){
            $nonceInt       = intval($nonceString);
            return true;
        }
        return false;
    }
}